package com.yuantu.judiciary.security.filter;

import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CountryResponse;
import com.yuantu.judiciary.common.SysCode;
import com.yuantu.judiciary.config.SecurityConfig;
import com.yuantu.judiciary.model.dto.AccountInfoDTO;
import com.yuantu.judiciary.service.IUserService;
import com.yuantu.judiciary.utils.JwtUtil;
import com.yuantu.judiciary.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * JWT登录授权过滤器
 *
 * @author syw
 */
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private IUserService userService;

    private static final File ipDB;

    private static final DatabaseReader reader;

    static {
        try {
            ipDB = new File("IpDB\\GeoLite2-Country.mmdb");
            //ipDB = new File("/IpDB/GeoLite2-Country.mmdb");
            reader = new DatabaseReader.Builder(ipDB).build();
        } catch (Exception e) {
            log.info("IP地址库加载失败");
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {

        //检测访问ip
        String remoteAddr = request.getRemoteAddr();
        if (!StringUtil.isEmpty(remoteAddr)) {
            try {
                log.info("访问ip:{}", remoteAddr);
                if (!"0:0:0:0:0:0:0:1".equals(remoteAddr) && !"127.0.0.1".equals(remoteAddr)) {
                    InetAddress ipAddress = InetAddress.getByName(remoteAddr);
                    CountryResponse countryResponse = reader.country(ipAddress);
                    if (!countryResponse.getCountry().getIsoCode().equals("CN")) {
                        JwtUtil.responseError(response, SysCode.IP_INVALID.getCode(), SysCode.IP_INVALID.getMessage());
                        return;
                    }
                }
            } catch (Exception e) {
                //shine 2024-06-11 增加异常捕获，防止IP库查询异常导致无法登录
//                throw new RuntimeException(e);

            }
        }

        // 静态资源放行
        if (Arrays.stream(SecurityConfig.KNIFE4J).anyMatch(uri -> Pattern.compile(uri).matcher(request.getServletPath()).find())) {
            filterChain.doFilter(request, response);
            return;
        }
        // 白名单放行
        if (request.getServletPath().startsWith("/profile")) {
            filterChain.doFilter(request, response);
            return;
        }
        if (Arrays.stream(JwtUtil.getWhiteList()).anyMatch(uri -> uri.equals(request.getServletPath()))) {
            filterChain.doFilter(request, response);
            return;
        }
        String token = null;
        try {
            token = JwtUtil.decodeTokenFromRequest(request, response);
        } catch (Exception e) {
            JwtUtil.responseError(response, SysCode.INVALID_TOKEN.getCode(), SysCode.INVALID_TOKEN.getMessage());
            return;
        }
//        String token = JwtUtil.decodeTokenFromRequest(request, response);
        // 判断令牌是否存在黑名单中
        if (token == null || JwtUtil.isInBlacklist(token)) {
            JwtUtil.responseError(response, SysCode.TOKEN_INVALID.getCode(), SysCode.TOKEN_INVALID.getMessage());
            return;
        }
        String accountId = JwtUtil.getAccountId(token);
        log.info("从token中拿到的accountId:{}", accountId);
        String endpoint = JwtUtil.getEndpoint(token);
        String key = endpoint + "_" + accountId;
        if (StringUtils.hasText(accountId) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
            AccountInfoDTO accountInfo = userService.getUserAccountInfoById(Long.parseLong(accountId));
            log.info("从数据库中拿到的accountInfo:{}", accountInfo);
            if (!JwtUtil.hasToken(key)) {
                JwtUtil.responseError(response, SysCode.ACCESS_TOKEN_EXPIRED.getCode(), SysCode.ACCESS_TOKEN_EXPIRED.getMessage());
                return;
            }
            // 校验令牌是否有效
            try {
                JwtUtil.decodeAccessToken(token);
                JwtUtil.checkTokenValid(token, accountInfo.getAccountId().toString());
            } catch (TokenExpiredException e) {
                JwtUtil.responseError(response, SysCode.ACCESS_TOKEN_EXPIRED.getCode(), SysCode.ACCESS_TOKEN_EXPIRED.getMessage());
                return;
            } catch (JWTVerificationException e) {
                JwtUtil.responseError(response, SysCode.TOKEN_INVALID.getCode(), SysCode.TOKEN_INVALID.getMessage());
                return;
            }
            // 权限信息
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(accountInfo, null, accountInfo.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

}
